package com.greenphosphor.glasshouse;

import javax.servlet.http.*;
import javax.servlet.*;
import java.util.*;
import java.io.*;
import java.sql.*;
import java.net.*;
import javax.net.ssl.*;
import java.util.zip.*;
import java.util.logging.*;

public class CICPManager extends HttpServlet {

  private static Hashtable<String, SLGraph> layerTable = new Hashtable<String, SLGraph>();
  private static Hashtable regionTable = new Hashtable();
  private Logger log;
  public static final int CHUNK_SIZE = 1900;
  public static long layerExpireMillis = 3600000;
  private int logThing = 0;
  private int nextGraphId = 1;

  public void init () throws ServletException {
    log = Logger.getLogger("cicp");
  }

  protected void doGet ( HttpServletRequest req, HttpServletResponse res ) throws ServletException, IOException {
    try {
      res.setContentType("text/html");
      PrintWriter pw = res.getWriter();
      String type = req.getParameter("type");
      String user = req.getParameter("user");
      String key = req.getParameter("key");

      String mode = getServletConfig().getInitParameter("mode");
      if ( mode != null && mode.equalsIgnoreCase("private") && (user == null || key == null) ) {
        pw.println("must supply user and key");
        pw.close();
        return;
      }
      else if ( mode != null && mode.equalsIgnoreCase("public") && (user != null || key != null) ) {
        pw.println("must use private service");
        pw.close();
        return;
      }

      String region = req.getParameter("region");
      if ( type.equals("msg") )
        processMsg(req, pw, user, key);
      else if ( type.equals("initregion") ) {
        int primCount = Integer.parseInt(req.getParameter("primcount"));
        initRegion(region, primCount);
      }
      else {
        int layer = Integer.parseInt(req.getParameter("layer"));
        //log.info("getting slg from key {" + String.valueOf(layer) + "}");
        SLGraph slg = (SLGraph)layerTable.get(String.valueOf(layer));
        //log.info("layer " + layer + " slg is " + (slg == null ? "null" : "not null"));
        if ( slg != null && ((user == null && slg.user != null) ||
             (user != null && slg.user == null) ||
             (key == null && slg.key != null) ||
             (key != null && slg.key == null) ||
             (user != null && !slg.user.equals(user)) ||
             (key != null  && !slg.key.equals(key))) )
          pw.println("bad credentials");
        else {
          if ( type.equals("menu") )
            printListChunk(pw, slg.menuList, slg.listCounts, 1);
          else if ( type.equals("dispatch") )
            printListChunk(pw, slg.dispatchList, slg.listCounts, 0);
          else if ( type.equals("chunk") ) {
            int chunk = Integer.parseInt(req.getParameter("chunk"));
            printChunk(pw, slg, chunk);
          }
        }
      }
      pw.close();
    }
    catch ( SQLException se ) {
      throw new ServletException("error: " + se, se);
    }
    catch ( IOException ie ) {
      throw new ServletException("error: " + ie, ie);
    }
  }

  private void initRegion ( String region, int primCount ) {
    RegionControl rc = new RegionControl(region, primCount, log);
    regionTable.put(region, rc);
    log.info("added (c23) region {" + region + "} with " + primCount + " cicp prims");
  }

  private void processMsg ( HttpServletRequest req, PrintWriter pw,
                            String user, String key ) throws IOException, SQLException {
    String msg = req.getParameter("msg");
    String region = req.getParameter("region");
    if ( user != null && key != null )
      msg += "," + user + "," + key;
    //log.info("received from {" + region + "}: " + msg);

    String rline = null;
    CICPProducer producer = null;

    int sourcelayer = 0;
    String[] split = null;
    SLGraph slg = null;
    if ( !msg.startsWith("init") ) {
      split = msg.split(",");
      sourcelayer = Integer.parseInt(split[0]);
      slg = (SLGraph)layerTable.get(String.valueOf(sourcelayer));
      producer = slg.producer;
      msg = String.valueOf(slg.producerLayer);
      for ( int i = 1; i < split.length; i++ )
        msg += "," + split[i];
    }
    else {
      if ( req.getParameter("prov_url") != null ) {
        String prov_url = URLDecoder.decode(req.getParameter("prov_url"), "UTF-8");
        producer = new CICPProducer(prov_url);
      }
      else {
        if(user !=null && key!=null){
          // no ssl; use the local private port
          producer = new CICPProducer("127.0.0.1", 4445);
        }
        else {
          String prov_host = "glasshouse.greenphosphor.com";
	  int prov_port = 4445;
	  if ( req.getParameter("prov_host") != null ) {
            prov_host = req.getParameter("prov_host");
            prov_port = Integer.parseInt(req.getParameter("prov_port"));
          }
          producer = new CICPProducer(prov_host, prov_port);
        }
      }
    }

    try {
      //log.info("sending {" + msg + "} to producer, logThing " + (++logThing));
      rline = producer.getResponse(msg);
    }
    catch ( IOException ie ) {
      log.info("exception processing msg: " + ie);
    }
    catch ( InterruptedException je ) {
      log.info("exception processing msg: " + je);
    }

    if ( !msg.startsWith("init") ) {
      if ( split[2].equals("-3") ) {
        if ( slg != null && !((user == null && slg.user != null) ||
             (user != null && slg.user == null) ||
             (key == null && slg.key != null) ||
             (key != null && slg.key == null) ||
             (user != null && !slg.user.equals(user)) ||
             (key != null  && !slg.key.equals(key))) ) {
          layerTable.remove(String.valueOf(sourcelayer));
          RegionControl rcd = (RegionControl)regionTable.get(region);
          if ( rcd != null ) {
            log.info("about to free prims for layer " + sourcelayer);
            rcd.freePrims(sourcelayer);
          }
        }
      }
    }

    if ( rline != null && (rline.startsWith("create") ||
                           rline.startsWith("flatImage") ||
                           rline.startsWith("modify")) ) {
      RegionControl rc = (RegionControl)regionTable.get(region);
      loadCells(rline, pw, rc, user, key, slg, producer);
    }
    else if ( rline != null && rline.startsWith("dump") ) {
      String[] split2 = rline.split(",");
      pw.println("dump," + split2[8]);
    }
    else
      pw.println("ok");
  }

  private void loadCells ( String rline, PrintWriter pw,
                           RegionControl rc, String user,
                           String key, SLGraph slg,
                           CICPProducer producer ) throws IOException {
    //log.info(rline);
    int mode = 0;
    String menuPrimId = null;
    String[] split = rline.split(",");
    int layer = Integer.parseInt(split[7]);
    LinkedList layerPrimList = null;
    if ( slg != null )
      layerPrimList = rc.getPrimList(slg.layer);
    double startX = Double.parseDouble(split[1]);
    double startY = Double.parseDouble(split[3]);
    double startZ = Double.parseDouble(split[2]);

    removeExpiredLayers();

    if ( slg == null || slg.producerLayer != layer ) {
      int ngid = getNextGraphId();
      slg = new SLGraph(ngid, startX, startY, startZ, rc, user, key, layer, producer);
      layerTable.put(String.valueOf(ngid), slg);
    }
    else {
      if ( (user == null && slg.user != null) ||
           (user != null && slg.user == null) ||
           (key == null && slg.key != null) ||
           (key != null && slg.key == null) ||
           (user != null && !slg.user.equals(user)) ||
           (key != null  && !slg.key.equals(key)) ) {
        pw.println("error: bad credentials");
        return;
      }
      slg.chunkList.clear();
      slg.dispatchList.clear();
      slg.menuList.clear();
      slg.listCounts[0] = 0;
      slg.listCounts[1] = 0;
      slg.timeMillis = System.currentTimeMillis();
    }
    boolean isMod = false;
    boolean isImage = false;
    if ( split[0].equals("modify") ) {
      isMod = true;
      mode = 2;
      menuPrimId = slg.menuPrimId;
    }
    else if ( split[0].equals("flatImage") )
      isImage = true;

    // hack to avoid the overhead of doing SSL get locally
    if ( split[8].startsWith("https") )
      split[8] = "http" + split[8].substring(5);
 
    URL url = new URL(split[8]);
    URLConnection conn = url.openConnection();
    BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
    String line;
    StringBuffer curChunk = new StringBuffer();
    String chunkPrimId = null;
    int chunkInd = 0;
    while ( (line = br.readLine()) != null ) {
      //log.info("line: " + line);
      if ( mode == 0 ) {
        slg.menuList.add(line);
        if ( line.equals("nodes") || line.equals("images") ) 
          mode = 1;
      }
      else {
        String[] lsplit = line.split(",");
        LinkedList<Integer> tempPrimList = new LinkedList<Integer>();
        if ( !lsplit[0].equals("create") && !lsplit[0].equals("flatImage") &&
             !lsplit[0].equals("modify") ) {
          String nextPrimId = null;
          boolean addToTempList = true;
          if ( isMod ) {
            Integer existPrim = slg.nodeMap.get(lsplit[19]);
            if ( lsplit[0].equals("del") ) {
              addToTempList = false;
              if ( existPrim != null ) {
                nextPrimId = String.valueOf(existPrim.intValue());
                if ( existPrim.intValue() != Integer.parseInt(slg.menuPrimId) )
                  slg.rc.freePrim(existPrim.intValue() - 1);
              }
              else
                log.info("null existPrim for node " + lsplit[1]);
            }
            else {
              if ( existPrim != null ) {
                nextPrimId = String.valueOf(existPrim.intValue());
                addToTempList = false;
              }
              else
                nextPrimId = slg.rc.getNextFreePrim(slg.layer, lsplit[19], slg.nodeMap);
            }
          }
          else if ( !isImage )
            nextPrimId = slg.rc.getNextFreePrim(slg.layer, lsplit[19], slg.nodeMap);
          else
            nextPrimId = slg.rc.getNextFreePrim(slg.layer, "0", slg.nodeMap);

          //log.info("got " + nextPrimId + " from rc for node " + lsplit[19]);
          if ( nextPrimId != null && nextPrimId.equals("full") ) {
            br.close();
            pw.println("error: not enough cicp prims available");
            rc.freePrims(tempPrimList, slg.layer);
            return;
          }
          else if ( nextPrimId != null && addToTempList )
            tempPrimList.add(new Integer(Integer.parseInt(nextPrimId) - 1));

          if ( menuPrimId == null ) {
            menuPrimId = nextPrimId;
            slg.menuPrimId = menuPrimId;
          }
          String lineAbs = null;
          if ( split[0].equals("flatImage") )
            lineAbs = line;
          else if ( lsplit[0].equals("del") )
            lineAbs = "del";
          else
            lineAbs = makeAbsolute(lsplit, slg, menuPrimId, slg.layer);
          if ( mode == 1 ) {
            slg.menuList.add(lineAbs);
            slg.dispatchList.add(nextPrimId + ";menu," + slg.layer);
            mode = 2;
          }
          else {
            if ( chunkPrimId == null )
              chunkPrimId = nextPrimId;
            if ( curChunk.length() + 2 + nextPrimId.length() + lineAbs.length() >= CHUNK_SIZE ) {
              slg.chunkList.add(curChunk);
              slg.dispatchList.add(chunkPrimId + ";chunk," + slg.layer + "," + chunkInd);
              chunkInd++;
              curChunk = new StringBuffer();
              chunkPrimId = nextPrimId;
            }
            else
              curChunk.append('\n');
            curChunk.append(nextPrimId);
            curChunk.append(';');
            curChunk.append(lineAbs);
          }
        }
      }
    }
    br.close();
    if ( curChunk.length() > 0 ) {
      slg.chunkList.add(curChunk);
      slg.dispatchList.add(chunkPrimId + ";chunk," + slg.layer + "," + chunkInd);
    }
    //log.info("graph has " + slg.chunkList.size() + " chunks");

    String directive = "new";
    if ( split[0].equals("modify") )
      directive = "mod";
    else if ( layerPrimList != null )
      rc.freePrims(layerPrimList, slg.layer);
    pw.println(directive);
    printListChunk(pw, slg.dispatchList, slg.listCounts, 0);
  }

  private void removeExpiredLayers(){
      Enumeration e = layerTable.keys();
      while(e.hasMoreElements()){
          String tempKey = (String) e.nextElement();
          SLGraph tld = (SLGraph) layerTable.get(tempKey);
          if(System.currentTimeMillis()-tld.timeMillis > layerExpireMillis){
              layerTable.remove(tempKey);
              log.info("Removed expired layer #: "+tld.layer);
          }
      }
  }

  private void printListChunk ( PrintWriter pw, ArrayList<String> list,
                                int[] counts, int countInd ) throws IOException {
    int charCount = 0;
    if ( countInd == 0 )
      charCount += 5;
    while ( counts[countInd] < list.size() &&
            charCount + 1 + (list.get(counts[countInd])).length() < CHUNK_SIZE ) {
      String s = list.get(counts[countInd]);
      pw.println(s);
      counts[countInd]++;
      charCount += s.length() + 1;
    }
    if ( counts[countInd] < list.size() )
      pw.println("more");
  }

  private void printChunk ( PrintWriter pw, SLGraph slg, int chunk ) throws IOException {
    StringBuffer sb = slg.chunkList.get(chunk);
    pw.println(sb.toString());
  }

  public static String slEncode ( String str ) {
    String encStr = str;
    try {
      encStr = URLEncoder.encode(str, "UTF-8");
    }
    catch ( UnsupportedEncodingException e ) {
      GPLogger.log("ALL","error in slEncode: " + e);
    }
    return encStr.replace("+", "%20");
  }

  private static String stripQuotes ( String str ) {
    return str.substring(1, str.length() - 1);
  }

  private String makeAbsolute ( String[] lsplit, SLGraph ld,
                                String menuPrimId, int layer ) {
    double relx = Double.parseDouble(lsplit[1]);
    double relz = Double.parseDouble(lsplit[2]);
    double rely = Double.parseDouble(lsplit[3]);
    StringBuffer sb = new StringBuffer(lsplit[0]);
    sb.append("," + String.valueOf((float)(relx + ld.startX)));
    sb.append("," + String.valueOf((float)(rely + ld.startY)));
    sb.append("," + String.valueOf((float)(relz + ld.startZ)));
    int startCopy = 4;
    if ( lsplit[0].equals("lin") ) {
      double x2 = Double.parseDouble(lsplit[4]);
      double z2 = Double.parseDouble(lsplit[5]);
      double y2 = Double.parseDouble(lsplit[6]);
      float x2f = (float)(x2 + ld.startX);
      float y2f = (float)(y2 + ld.startY);
      float z2f = (float)(z2 + ld.startZ);
      sb.append("," + String.valueOf(x2f));
      sb.append("," + String.valueOf(y2f));
      sb.append("," + String.valueOf(z2f));
      startCopy = 7;
    }
    for ( int i = startCopy; i < lsplit.length; i++ )
      sb.append("," + lsplit[i]);
    sb.append("," + menuPrimId);
    sb.append("," + layer);
    return sb.toString();
  }

  private synchronized int getNextGraphId () {
    int id = nextGraphId;
    nextGraphId++;
    if ( nextGraphId == 999999 )
      nextGraphId = 1;
    return id;
  }
}
